{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "d4827057-2a77-464b-bb64-f5d759dc7b69",
   "metadata": {},
   "source": [
    "# Chain模块代码实战"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8627a2eb-862f-4517-9e17-ae4ca78eb456",
   "metadata": {},
   "outputs": [],
   "source": [
    "## 导入依赖库\n",
    "\n",
    "!pip install langchain"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0268fc99-1e09-4f33-b295-e210d78a558c",
   "metadata": {},
   "source": [
    "## 1. LLM"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "855ab241-1f3b-4e1b-b917-2a62115388eb",
   "metadata": {},
   "source": [
    "LLMChain: 简单的LLM链\n",
    "使用LangChain表达式语言来 实现最常见的链式操作类型\n",
    "\n",
    "在任何LLM应用中，最常见的链接方式是将提示模板与LLM和可选的输出解析器组合起来。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "ebac3146-a629-4dac-bb92-ade33d041292",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\"Rainbow Footwear Co.\"\n"
     ]
    }
   ],
   "source": [
    "from langchain.prompts import PromptTemplate\n",
    "#from langchain.chat_models import ChatOpenAI\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "from langchain.schema import StrOutputParser\n",
    "\n",
    "\n",
    "\n",
    "prompt = PromptTemplate.from_template(\n",
    "    \"What is a good name for a company that makes {product}?\"\n",
    ")\n",
    "\n",
    "chat_model = ChatOpenAI(model_name=\"gpt-3.5-turbo-0125\")\n",
    "## LCEL chain表达式语言， 利用管道符“|” 实现 链式调用\n",
    "runnable = prompt | chat_model | StrOutputParser()\n",
    "\n",
    "result = runnable.invoke({\"product\": \"colorful socks\"})\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81d92793-3664-4b28-b58a-15d90730003a",
   "metadata": {
    "tags": []
   },
   "source": [
    "以前的API（不推荐使用，但是建议了解一下）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "207c2a63-d34b-418d-a1af-951419cddc23",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'product': 'colorful socks', 'text': 'Rainbow Threads'}\n"
     ]
    }
   ],
   "source": [
    "from langchain.prompts import PromptTemplate\n",
    "from langchain.llms import OpenAI\n",
    "#from langchain_openai import OpenAI\n",
    "from langchain.chains import LLMChain\n",
    "\n",
    "prompt_template = \"What is a good name for a company that makes {product}?\"\n",
    "\n",
    "llm = OpenAI(model_name=\"gpt-3.5-turbo\")\n",
    "\n",
    "llm_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template))\n",
    "\n",
    "result = llm_chain(\"colorful socks\")\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2e00d499-f08d-4a3b-b626-17781c7a0006",
   "metadata": {},
   "source": [
    "## 2.Sequential"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ef6c3338-5d94-4f4a-894f-938c77ccda68",
   "metadata": {},
   "source": [
    "SequentialChain: 串联式调用语言模型链 （将多条链路组合串起来使用）\n",
    "在调用语言模型之后，下一步是对语言模型进行一系列的调用。当您希望将一次调用的输出作为另一次调用的输入时，这是特别有用的。\n",
    "比如：\n",
    "第一次调用语言模型（prompmt1|model|parse1)\n",
    "第二次调用语言模型（prompmt2|model|parse2)\n",
    "第一次的parse1，作为第二次的prompmt2输入内容\n",
    "\n",
    "示例: 假设我们想创建一个链，首先创建剧情简介，然后根据简介生成一篇剧评。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "fe873765-8ad8-4ab8-8001-bcbf2023de0c",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from langchain.prompts import PromptTemplate\n",
    "\n",
    "synopsis_prompt = PromptTemplate.from_template(\n",
    "    \"\"\"你是一位剧作家。给定一个剧目的标题，你的任务是为这个标题写一个剧情简介。\n",
    "\n",
    "标题: {title}\n",
    "剧作家: 这是上述剧目的剧情简介:\"\"\"\n",
    ")\n",
    "\n",
    "review_prompt = PromptTemplate.from_template(\n",
    "    \"\"\"您是《纽约时报》的戏剧评论家。根据剧情简介，您的工作是为该剧撰写一篇评论。.\n",
    "\n",
    "剧情简介:\n",
    "{synopsis}\n",
    "上述剧目的《纽约时报》剧评家的评论:\"\"\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "fb7ef59e-9fbe-4291-8c2f-8d5e159f7b6e",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\"日落时的海滩悲剧\"是一部呼唤我们对人性和命运无常深入反思的剧作。剧本巧妙地通过主角杰克的个人经历，充满情感的描绘了对失去的爱和自我救赎的渴望，使观众深感共鸣。\n",
      "\n",
      "故事的环境设置在美丽而神秘的海滩，这样的背景为本剧增添了一种独特的情感色彩。海滩，即是杰克与莉莉曾经的甜蜜乐园，也是杰克痛苦的源泉。这种强烈的对比使得剧情更加引人入胜。\n",
      "\n",
      "杰克的角色塑造尤为出色。他的坚毅和勇敢，他对过去的痛苦回忆，以及他在寻求救赎的过程中的挣扎，都让人深感同情。而他在面对莉莉死亡真相时的决心和勇气，令人印象深刻。\n",
      "\n",
      "在剧中出现的其他角色也丰富了剧情的层次感。神秘的海滩管家、曾目睹莉莉事故的渔夫，以及善良热情的艾米，她们的出现给杰克的生活带来了新的转机，也为观众提供了对主题的更多思考。\n",
      "\n",
      "\"日落时的海滩悲剧\"的剧情独特，复杂而引人入胜。剧中的每一个情节都充满了戏剧张力，每一场戏都能触动人心。最重要的是，这部剧让我们看到了人性中的矛盾和复杂，也看到了在痛苦和希望中寻找救赎的可能。\n",
      "\n",
      "总的来说，\"日落时的海滩悲剧\"是一部揭示人性深层次的剧作，充满了悲剧和希望。它不仅令人深思，也让人心生敬畏。我强烈推荐观众观看这部作品，去感受它带来的情感冲击和人性的反思。\n"
     ]
    }
   ],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "from langchain.schema import StrOutputParser\n",
    "\n",
    "#llm = ChatOpenAI(model_name=\"gpt-4\")\n",
    "llm = ChatOpenAI(model_name=\"gpt-3.5-turbo-0125\")\n",
    "\n",
    "chain = (\n",
    "    # 将第一次调用的结果放到参数synopsis中，再作为第二次review_prompt的输入\n",
    "    {\"synopsis\": synopsis_prompt | llm | StrOutputParser()}\n",
    "    | review_prompt\n",
    "    | llm\n",
    "    | StrOutputParser()\n",
    ")\n",
    "result = chain.invoke({\"title\": \"日落时的海滩悲剧\"})\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "cf6d8f4b-dc2a-452d-9a09-ed774d304104",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'synopsis': '\"日落时的海滩悲剧\"是一部深情且引人深思的戏剧，描绘了在一个偏远海滩发生的一系列悲剧事件。故事围绕着两个家庭展开，他们在这个风景如画的海滩上过着平静的生活，直到一个可怕的秘密被揭露，改变了他们所有人的生活。\\n\\n海滩的主人，老渔夫约瑟夫，因为一个神秘的过去而寂寞地生活着。另一方面，刚搬到海滩的年轻家庭，由年轻的夫妇安娜和汤姆以及他们的小女儿莉莉组成，他们希望在这里开始新的生活。然而，当一个不速之客在日落时出现，揭示了约瑟夫的过去，这个宁静的海滩变成了一片混乱。\\n\\n这个侵入者是约瑟夫的旧知己，他带来了一个令人震惊的消息，约瑟夫的过去并不像他所描述的那样简单。他曾是一名海盗，被他的同伴背叛，被迫独自逃生。他的同伴为了掩盖真相，制造了一个关于约瑟夫的谎言。\\n\\n当这个秘密被揭露，海滩的宁静被打破，约瑟夫被迫面对他的过去，同时安娜和汤姆也被卷入了这场风暴。这部剧以一种深情而悲壮的方式描绘了人性的复杂性，以及面对过去和寻找救赎的艰难过程。', 'review': '《日落时的海滩悲剧》是一部引人深思的戏剧，它在揭示人性的微妙复杂性方面展现了出色的表现力。剧作家通过这部作品，巧妙地探讨了过去的阴影如何在未预料的瞬间破坏现在的安宁，以及人们如何在混乱中寻找救赎。\\n\\n首先，剧中的角色塑造极具深度。约瑟夫，这个老渔夫，被描绘成一个拥有神秘过去的人，他的寂寞和痛苦让人同情。另一方面，对新家庭的描述也充满了细腻的笔触，年轻的夫妇安娜和汤姆以及他们的小女儿莉莉，他们的希望和恐惧都让这个家庭显得栩栩如生。\\n\\n随着剧情的推进，不速之客的出现，揭示了约瑟夫的过去，这一点转折尤其出色。这个揭示不仅改变了角色们的生活，也改变了观众对约瑟夫的看法。他的过去并不像他所描述的那样简单，他曾是一名海盗，这种复杂的背景增加了剧情的张力，并为后续的冲突和纠葛做了铺垫。\\n\\n然而，本剧的真正亮点在于，它以一种深情而悲壮的方式描绘了人性的复杂性，以及面对过去和寻找救赎的艰难过程。约瑟夫被迫面对他的过去，安娜和汤姆也被卷入了这场风暴，他们的挣扎，他们的冲突，以及他们的最终救赎，都以一种深情而引人深思的方式呈现出来。\\n\\n总的来说，《日落时的海滩悲剧》是一部引人深思的戏剧，它以精彩的剧情和复杂的人物塑造展现了生活的真实性和人性的复杂性。对于喜欢深度剧情和复杂角色塑造的观众来说，这是一部不容错过的作品。'}\n"
     ]
    }
   ],
   "source": [
    "#如果我们还想要回到概要，我们可以这样做\n",
    "from langchain.schema.runnable import RunnablePassthrough\n",
    "\n",
    "synopsis_chain = synopsis_prompt | llm | StrOutputParser()\n",
    "review_chain = review_prompt | llm | StrOutputParser()\n",
    "# 定义一个字典{\"synopsis\": synopsis_chain}；\n",
    "# 再给到RunnablePassthrough.assign() 方法，将其与 review_chain 合并；\n",
    "# RunnablePassthrough.assign()返回一个新的对象给chain\n",
    "chain = {\"synopsis\": synopsis_chain} | RunnablePassthrough.assign(review=review_chain)\n",
    "# 执行invoke时会自动将上一条链的输出，作为下一条链的输入\n",
    "result = chain.invoke({\"title\": \"日落时的海滩悲剧\"})\n",
    "print(result)\n",
    "\n",
    "\n",
    "#RunnablePassthrough.assign 方法说明：\n",
    "# 接受一个或多个键值对作为参数，其中键表示链路的名称，值表示链路本身。在这个例子中，\n",
    "# 我们传入了两个键值对：\"synopsis\": synopsis_chain 和 \"review\": review_chain。\n",
    "# 当调用 RunnablePassthrough.assign() 方法时，它会创建一个新的 RunnablePassthrough 对象，\n",
    "# 并将传入的链路添加到该对象中。这个新对象可以像字典一样使用，通过链路名称来访问和执行相应的链路"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "938f06ce-cc63-4946-b21e-003d3fcd9880",
   "metadata": {},
   "source": [
    "## 3. Transformation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0fe73fe5-7ef9-48b6-ac65-4e83b6f2383b",
   "metadata": {},
   "source": [
    "转换：数据传递过程中对其进行转换处理\n",
    "\n",
    "例如，我们将创建一个虚拟转换，它接收一个超长的文本，将文本筛选为前三段，然后将其传递到一个链中进行摘要。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "95965572-5dfc-40cf-ac08-abb7ab1e228c",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from langchain.prompts import PromptTemplate\n",
    "\n",
    "prompt = PromptTemplate.from_template(\n",
    "    \"\"\"对下面内容进行总结摘要:\n",
    "{output_text}\n",
    "摘要:\"\"\"\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "27a8d4cd-e751-4e87-a8a4-76f9e6ca8baa",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "with open(\"消失的她.txt\",'r', encoding='utf-8') as f:\n",
    "    state_of_the_union = f.read()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "835853fd-e828-40e0-8eba-db3b9f943875",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "电影《消失的她》讲述了丈夫何非的妻子李木子在结婚周年旅行中神秘失踪的故事。出现了一个陌生女人冒充李木子，并引发了一系列扑朔迷离的事件。情节复杂，存在矛盾和谜团，观众需猜测真相。\n"
     ]
    }
   ],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "from langchain.schema import StrOutputParser\n",
    "\n",
    "llm = ChatOpenAI(model_name=\"gpt-3.5-turbo-0125\")\n",
    "\n",
    "runnable = (\n",
    "    {\"output_text\": lambda text: \"\\n\\n\".join(text.split(\"\\n\\n\")[:3])}\n",
    "    | prompt\n",
    "    | llm\n",
    "    | StrOutputParser()\n",
    ")\n",
    "result = runnable.invoke(state_of_the_union)\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d942da1e-73b8-4018-ab9c-c9034e077d56",
   "metadata": {},
   "source": [
    "## 4. Router"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b781a9c4-9ce3-43f6-9d5f-bc902dcf0497",
   "metadata": {},
   "source": [
    "RouterChain: 实现条件判断的大模型调用\n",
    "\n",
    "路由允许创建非确定性链，其中前一步的输出定义下一步。路由有助于在与LLMs的交互中提供结构和一致性。\n",
    "\n",
    "例子: 假设我们有两个针对不同类型问题进行优化的模板，并且我们希望根据用户输入选择模板。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "2ebd777c-933e-4c76-94a1-bec6998c29db",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from langchain.prompts import PromptTemplate\n",
    "\n",
    "## 物理学家的模板\n",
    "physics_template = \"\"\"You are a very smart physics professor. \\\n",
    "You are great at answering questions about physics in a concise and easy to understand manner. \\\n",
    "When you don't know the answer to a question you admit that you don't know.\n",
    "\n",
    "Here is a question:\n",
    "{input}\"\"\"\n",
    "physics_prompt = PromptTemplate.from_template(physics_template)\n",
    "\n",
    "## 数学家的模板\n",
    "math_template = \"\"\"You are a very good mathematician. You are great at answering math questions. \\\n",
    "You are so good because you are able to break down hard problems into their component parts, \\\n",
    "answer the component parts, and then put them together to answer the broader question.\n",
    "\n",
    "Here is a question:\n",
    "{input}\"\"\"\n",
    "math_prompt = PromptTemplate.from_template(math_template)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "2d2b72a4-934a-4808-9592-3a4d3988207c",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "from langchain.schema.output_parser import StrOutputParser\n",
    "from langchain.schema.runnable import RunnableBranch\n",
    "\n",
    "## 其他通用问题的模块\n",
    "general_prompt = PromptTemplate.from_template(\n",
    "    \"You are a helpful assistant. Answer the question as accurately as you can.\\n\\n{input}\"\n",
    ")\n",
    "\n",
    "##路由分支\n",
    "prompt_branch = RunnableBranch(\n",
    "    (lambda x: x[\"topic\"] == \"math\", math_prompt),\n",
    "    (lambda x: x[\"topic\"] == \"physics\", physics_prompt),\n",
    "    general_prompt,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "ec705384-a426-4eee-8da2-aefe516ad9a8",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from typing import Literal\n",
    "\n",
    "from langchain.pydantic_v1 import BaseModel\n",
    "from langchain.output_parsers.openai_functions import PydanticAttrOutputFunctionsParser\n",
    "from langchain.utils.openai_functions import convert_pydantic_to_openai_function\n",
    "\n",
    "## 对用户问题分类，定义函数\n",
    "class TopicClassifier(BaseModel):\n",
    "    \"Classify the topic of the user question\"\n",
    "\n",
    "    topic: Literal[\"math\", \"physics\", \"general\"]\n",
    "    \"The topic of the user question. One of 'math', 'physics' or 'general'.\"\n",
    "\n",
    "## 转化为OpenAI function函数\n",
    "classifier_function = convert_pydantic_to_openai_function(TopicClassifier)\n",
    "\n",
    "## 创建大模型\n",
    "llm = ChatOpenAI(model_name=\"gpt-3.5-turbo\").bind(\n",
    "    functions=[classifier_function], function_call={\"name\": \"TopicClassifier\"}\n",
    ")\n",
    "## 定义对输出进行解析\n",
    "## 输出的对象TopicClassifier的属性值topic\n",
    "parser = PydanticAttrOutputFunctionsParser(\n",
    "    pydantic_schema=TopicClassifier, attr_name=\"topic\"\n",
    ")\n",
    "## 基于大模型对输出进行解析\n",
    "classifier_chain = llm | parser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "c64fb2af-ca88-4a94-a52e-421631736f8f",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from operator import itemgetter\n",
    "\n",
    "from langchain.schema.output_parser import StrOutputParser\n",
    "from langchain.schema.runnable import RunnablePassthrough\n",
    "\n",
    "\n",
    "final_chain = (\n",
    "    RunnablePassthrough.assign(topic=itemgetter(\"input\") | classifier_chain)\n",
    "    | prompt_branch\n",
    "    | ChatOpenAI(model_name=\"gpt-3.5-turbo-0125\")\n",
    "    | StrOutputParser()\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "90567e73-4032-4a14-a2b4-8b69409d6cfd",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"Thank you for your kind words! I'm happy to help you with this math question.\\n\\nTo find the first prime number greater than 40 such that one plus the prime number is divisible by 3, we can follow these steps:\\n\\n1. Start by listing the prime numbers greater than 40. The prime numbers after 40 are 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, and so on.\\n\\n2. For each prime number, add one to it and check if the resulting number is divisible by 3.\\n\\nLet's go through the list:\\n\\n- For 41, one plus 41 equals 42, which is divisible by 3. However, 41 itself is not divisible by 3.\\n- For 43, one plus 43 equals 44, which is not divisible by 3.\\n- For 47, one plus 47 equals 48, which is divisible by 3. However, 47 itself is not divisible by 3.\\n\\nWe can continue this process until we find the first prime number that satisfies the condition. \\n\\nLet's check the next prime number:\\n\\n- For 53, one plus 53 equals 54, which is divisible by 3. Therefore, the first prime number greater than 40 such that one plus the prime number is divisible by 3 is 53.\\n\\nSo, the answer to the question is 53.\\n\\nIf you have any further questions or need assistance with any other math problems, feel free to ask!\""
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "## 什么是大于40的第一个质数，使得这个质数加一可被3整除？\n",
    "result = final_chain.invoke(\n",
    "    {\n",
    "        \"input\": \"What is the first prime number greater than 40 such that one plus the prime number is divisible by 3?\"\n",
    "    }\n",
    ")\n",
    "\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "95338110-942d-4bf0-920d-192e6f853d40",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2e344c68-f0cf-40f7-974b-2463ef63c83f",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
